package com.du.fileload.framework.utils;

import com.du.fileload.framework.config.DFileCompressRatio;
import com.du.fileload.framework.config.DFileLoad;
import com.du.fileload.framework.entity.RequestFileDataBody;
import com.du.fileload.framework.entity.ResourcePathNameFormatENUM;
import com.du.fileload.framework.exception.FileNameLengthLimitExceededException;
import com.du.fileload.framework.exception.FileSizeLimitExceededException;
import com.du.fileload.framework.exception.InvalidExtensionException;
import com.du.plugin.common.tool.HandleException;
import com.du.plugin.common.tool.UUIDStr;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Matcher;

/**
 * 文件上传工具类
 * 
 * @author fx
 */
public class FileUploadUtils {

	private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);

	/**
	 * 默认大小 50M
	 */
	public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

	/**
	 * 默认的文件名最大长度 100
	 */
	public static final int DEFAULT_FILE_NAME_LENGTH = 100;

	/**
	 * 默认上传的地址
	 */
	private static String defaultBaseDir;

	private static ResourcePathNameFormatENUM resourcePathNameFormat;

	private static int counter = 0;

	public static void setDefaultBaseDir(String defaultBaseDir) {
		FileUploadUtils.defaultBaseDir = defaultBaseDir;
	}

	public static String getDefaultBaseDir() {
		return defaultBaseDir;
	}

	public static ResourcePathNameFormatENUM getResourcePathNameFormat() {
		return resourcePathNameFormat;
	}

	public static void setResourcePathNameFormat(ResourcePathNameFormatENUM resourcePathNameFormat) {
		FileUploadUtils.resourcePathNameFormat = resourcePathNameFormat;
	}

	/**
	 * 以默认配置进行文件上传
	 *
	 * @param fileDataBody
	 *            上传的文件
	 * @return 文件名称
	 * @throws Exception
	 */
	public static final String upload(RequestFileDataBody fileDataBody) throws IOException {
		try {
			return upload(getDefaultBaseDir(), fileDataBody, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
		} catch (Exception e) {
			throw new IOException(e.getMessage(), e);
		}
	}

	/**
	 * 根据文件路径上传
	 *
	 * @param baseDir
	 *            相对应用的基目录
	 * @param fileDataBody
	 *            上传的文件
	 * @return 文件名称
	 * @throws IOException
	 */
	public static final String upload(String baseDir, RequestFileDataBody fileDataBody) throws IOException {
		try {
			return upload(baseDir, fileDataBody, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
		} catch (Exception e) {
			throw new IOException(e.getMessage(), e);
		}
	}

	/**
	 * 文件上传
	 *
	 * @param baseDir
	 *            相对应用的基目录
	 * @param fileDataBody
	 *            上传的文件
	 *            上传文件类型
	 * @return 返回上传成功的文件名
	 * @throws FileSizeLimitExceededException
	 *             如果超出最大大小
	 * @throws FileNameLengthLimitExceededException
	 *             文件名太长
	 * @throws IOException
	 *             比如读写文件出错时
	 * @throws InvalidExtensionException
	 *             文件校验异常
	 */
	public static final String upload(String baseDir, RequestFileDataBody fileDataBody, String[] allowedExtension)
			throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException {
		int fileNamelength = fileDataBody.getFile().getOriginalFilename().length();
		if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
			throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
		}
		assertAllowed(fileDataBody.getFile(), allowedExtension);
		String fileName = extractFilename(fileDataBody.getFile());
		File desc = getFileSavePath(baseDir, fileName);
		fileDataBody.getFile().transferTo(desc);

		//进行文件压缩 压缩完成会返回自定义文件名称
		int compressLevle=DFileLoad.getInstance().getCompress().getCompressLevle();
		if(compressLevle!=0&&fileDataBody.getCl()!=null){
			if(fileDataBody.getCl()!=0){
				//使用自定义级别压缩
				fileName=compressResource(fileName,fileDataBody.getCl());
			}else{
				//使用默认级别压缩
				fileName=compressResource(fileName,compressLevle);
			}
		}
		return getRealPathFileName(DFileLoad.getInstance().getRequestPrefix()+fileName);
	}

	/**
	 * 获取文件相对路径
	 * @param fileName
	 * @return
	 * @throws IOException
	 */
	public static final String getRealPathFileName(String fileName) throws IOException {
		String pathFileName =String.format("%s",fileName.replaceAll("\\\\","/"));
		return pathFileName;
	}

	/**
	 * 获取文件绝对路径  包含磁盘绝对位置
	 * @param fileName
	 * @return
	 * @throws IOException
	 */
	public static final String getAbsolPathFileName(String fileName) throws IOException {
		String pathFileName =String.format("%s%s",DFileLoad.getInstance().getResourcePath(),fileName.replace(DFileLoad.getInstance().getRequestPrefix(), ""));
		pathFileName=pathFileName.replaceAll("/", Matcher.quoteReplacement(File.separator));
		return pathFileName;
	}

	/**
	 * 获取文件绝对路径  不包含磁盘绝对位置
	 * @param fileName
	 * @return
	 * @throws IOException
	 */
	public static final String toAbsolPathFileName(String fileName)  {
		String pathFileName =String.format("%s",fileName.replace(DFileLoad.getInstance().getRequestPrefix(), ""));
		pathFileName=pathFileName.replaceAll("/", Matcher.quoteReplacement(File.separator));
		return pathFileName;
	}

	/**
	 * 获取文件存储地址
	 * @param uploadDir
	 * @param fileName
	 * @return
	 * @throws IOException
	 */
	public static final File getFileSavePath(String uploadDir, String fileName) throws IOException {
		File desc = new File(uploadDir + fileName);
		if (!desc.getParentFile().exists()) {
			desc.getParentFile().mkdirs();
		}
		if (!desc.exists()) {
			desc.createNewFile();
		}
		return desc;
	}

	/**
	 * 编码文件名以及文件夹
	 */
	public static final String extractFilename(MultipartFile file) {
		String fileName = file.getOriginalFilename();
		String extension = getExtension(file);
		fileName = encodingFoldername() +File.separator+ encodingFilename(fileName,extension) ;
		return fileName;
	}

	/**
	 * 编码文件名
	 */
	private static final String encodingFilename(String fileName,String extension) {
		return String.format("%s.%s", UUIDStr.getUUID_32(), extension);
	}



	/**
	 * 编码文件夹名
	 */
	private static final String encodingFoldername() {
		Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
		Integer month=cal.get(Calendar.MONTH) + 1;
		Integer day=cal.get(Calendar.DATE);
		String monthStr=month > 10 ? month.toString() : "0" + month;
		String dayStr=day > 10 ? day.toString() : "0" + day;
		String folderName=null;
		switch (getResourcePathNameFormat()){
			case DATETIME:
				// format=/yyyymmdd
				folderName=folderName=String.format("$s%s%s%s",File.separator,cal.get(Calendar.YEAR) ,monthStr,dayStr );
				break;
			case DATE:
				// format=/yyyy/mm/dd
				folderName=String.format("%s%s%s%s%s%s",File.separator,cal.get(Calendar.YEAR) ,File.separator,monthStr,File.separator,dayStr );
			default:
		}
		File file=new File(getDefaultBaseDir()+folderName);
		if(!file.exists()){
			file.mkdirs();
		}
		return folderName;
	}

	/**
	 * 文件大小校验
	 *
	 * @param file
	 *            上传的文件
	 * @return
	 * @throws FileSizeLimitExceededException
	 *             如果超出最大大小
	 * @throws InvalidExtensionException
	 */
	public static final void assertAllowed(MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, InvalidExtensionException {
		long size = file.getSize();
		if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE) {
			throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
		}

		String fileName = file.getOriginalFilename();
		String extension = getExtension(file);
		if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
			if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
				throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName);
			} else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
				throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName);
			} else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
				throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName);
			} else {
				throw new InvalidExtensionException(allowedExtension, extension, fileName);
			}
		}

	}

	/**
	 * 判断MIME类型是否是允许的MIME类型
	 *
	 * @param extension
	 * @param allowedExtension
	 * @return
	 */
	public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
		for (String str : allowedExtension) {
			if (str.equalsIgnoreCase(extension)) {
				return true;
			}
		}
		return false;
	}



	/**
	 * 获取文件名的后缀
	 * 
	 * @param file
	 *            表单文件
	 * @return 后缀名
	 */
	public static final String getExtension(MultipartFile file) {
		String extension = FilenameUtils.getExtension(file.getOriginalFilename());
		if (StringUtils.isEmpty(extension)) {
			extension = MimeTypeUtils.getExtension(file.getContentType());
		}
		return extension;
	}

	/**
	 * 压缩资源 根据压缩级别 重命名文件名  例如 compressLevle=3 转换后  name_x1_x2_x3
	 * @return
	 */
	private static String compressResource(String fileName,int compressLevle) {
		List<DFileCompressRatio> compressRatio =DFileLoad.getInstance().getCompress().getCompressRatio();
		if(compressRatio==null||compressRatio.size()==0){
			log.warn("图片压缩失败,配置参数为空："+fileName);
			return fileName;
		}
		if(compressRatio.size()<compressLevle){
			log.warn("图片压缩失败,当前配置不满足压缩级别设定："+fileName);
			return fileName;
		}
		File file = new File(defaultBaseDir + fileName);
		if(file.length()<DFileLoad.getInstance().getCompress().getLimitLen()){
			log.warn("图片压缩跳过,容量检测过滤："+fileName);
			return fileName;
		}

		String prefix=file.getName().substring(file.getName().lastIndexOf("."));//如果想获得不带点的后缀，变为fileName.lastIndexOf(".")+1
		int num=prefix.length();//得到后缀名长度
		String oldFileName=file.getName().substring(0, file.getName().length()-num);//得到文件名。去掉了后缀
		String newFileName=oldFileName;
		DFileCompressRatio fileCompressRatio;
		BufferedImage BI;
		try {
			 BI  = ImageIO.read(file);;
		}catch (Exception e){
			log.warn("图片压缩失败,资源读取出错："+fileName);
			HandleException.addEx(e);
			return fileName;
		}
		for (int i=0;i<compressLevle;i++){
			newFileName+=String.format("_x%s",i+1);
			//压缩后文件存储路径s
			String curFileName=defaultBaseDir + fileName.replace(oldFileName,String.format("%s_x%s",oldFileName,i+1));
			fileCompressRatio=compressRatio.get(i);
			String widthStr=fileCompressRatio.getWidth();
			String heightStr=fileCompressRatio.getHeight();
			int width=0;
			int height=0;
			Float quality=fileCompressRatio.getQuality();
			int imgWidth = BI.getWidth(); // 源图宽度
			int imgHeight = BI.getHeight(); // 源图高度
			if(widthStr!=null){
				if(widthStr.contains("%")){
					width=Integer.valueOf(widthStr.replace("%",""));
					width= BigDecimal.valueOf(imgWidth).multiply(BigDecimal.valueOf(width).divide(BigDecimal.valueOf(100),2, BigDecimal.ROUND_DOWN)).intValue();
				}else{
					width=Integer.valueOf(widthStr);
				}
				if(imgWidth<width)
					width=imgWidth;
			}else{
				width=imgWidth;
			}

			if(heightStr!=null){
				if(heightStr.contains("%")){
					height=Integer.valueOf(widthStr.replace("%",""));
					height=BigDecimal.valueOf(imgHeight).multiply(BigDecimal.valueOf(height).divide(BigDecimal.valueOf(100),2, BigDecimal.ROUND_DOWN)).intValue();
				}else{
					height=Integer.valueOf(widthStr);
				}
				if(imgHeight<height)
					height=imgHeight;
			}else{
				height=imgHeight;
			}

			if(width==imgWidth&&height==imgHeight&&(quality==null||quality==1)){
				//如果宽高一致则跳过压缩，直接使用原图
				continue;
			}

			try {
				if(quality!=null){
					Thumbnails.of(file).size(width, height).outputQuality(quality).toFile(curFileName);
				}else{
					Thumbnails.of(file).size(width, height).toFile(curFileName);
				}
			}catch (Exception e){
				log.warn("图片压缩失败："+fileName);
				HandleException.addEx(e);
				return fileName;
			}
		}
		newFileName=fileName.replace(oldFileName,newFileName);
		return newFileName;
	}

	/**
	 * 图片压缩API  传入图片相对位置url
	 * @param fileUrl
	 * @param compressLevle
	 * @return
	 */
	public static String compressResourceApi(String fileUrl,int compressLevle) {
		String fileName=toAbsolPathFileName(fileUrl);
		return compressResource( fileName, compressLevle);
	}

}